#include <basic/errcode.h>
#include <basic/para.h>

char *para_get_item(char *str, char **pcurr)
{/*{{{*/
    char buf[10];
    int strsize;
    int itemsize=-1;
    char *item=(char *)NULL;

    if(!str || !pcurr)
        return((char *)NULL);
    if(*pcurr)
    {
        if(*pcurr < str)
            return((char *)NULL);
    }
    else
        *pcurr=str;
    if((strsize=strlen(*pcurr)) < PARA_ITEM_SIZE_CHAR+1+1)
        return((char *)NULL);

    item=*pcurr+PARA_ITEM_SIZE_CHAR+1;
    if(*(item-1) != PARA_ITEM_DLM)
        return((char *)NULL);
    strncpy(buf, *pcurr, PARA_ITEM_SIZE_CHAR);
    buf[PARA_ITEM_SIZE_CHAR]=ZERO;
    sscanf(buf, "%X", &itemsize);
    if(itemsize < 0 || itemsize > *pcurr+strsize-item-1 ||
            item[itemsize] != PARA_ITEM_DLM)
        return((char *)NULL);
    item[itemsize]=ZERO;
    *pcurr=item+itemsize+1;

    return(item);
}/*}}}*/

char *para_peek_item(char *str, char **pcurr)
{/*{{{*/
    int strsize;
    char *p=(char *)NULL;
    char buf[10];
    int itemsize=-1;
    char *item=(char *)NULL;
    
    if(!str || !pcurr)
        return((char *)NULL);
    if(*pcurr)
    {
        if(*pcurr < str)
            return((char *)NULL);
    }
    else
        *pcurr=str;
    if((strsize=strlen(*pcurr)) < PARA_ITEM_SIZE_CHAR+1+1)
        return((char *)NULL);

    p=*pcurr+PARA_ITEM_SIZE_CHAR+1;
    if(*(p-1) != PARA_ITEM_DLM)
        return((char *)NULL);
    strncpy(buf, *pcurr, PARA_ITEM_SIZE_CHAR);
    buf[PARA_ITEM_SIZE_CHAR]=ZERO;
    sscanf(buf, "%X", &itemsize);
    if(itemsize < 0 || itemsize > *pcurr+strsize-p-1 ||
            p[itemsize] != PARA_ITEM_DLM)
        return((char *)NULL);
    ALLOC_MULTI_RETERR(item, char, itemsize+1, (char *)NULL);
    strncpy(item, p, itemsize);
    item[itemsize]=ZERO;
    *pcurr=p+itemsize+1;

    return(item);
}/*}}}*/

char *para_pop_item(char *str)
{/*{{{*/
    int strsize;
    char *p;
    char *sizedlm, *itemdlm;
    char *item;
    int itemsize=-1;
    char buf[10];

    if(!str || BUF_IS_CLR(str))
        return((char *)NULL);
    strsize=strlen(str);

    p=str+strsize-1;
    if(*p != PARA_ITEM_DLM)
        return((char *)NULL);
    itemdlm=p;
    p--;
    while(*p != PARA_ITEM_DLM && p > str)  p--;
    if(p == str || p-PARA_ITEM_SIZE_CHAR < str)
        return((char *)NULL);
    item=p+1;
    sizedlm=p;
    p-=PARA_ITEM_SIZE_CHAR;
    if(p < str)
        return((char *)NULL);
    if(p > str && *(p-1) != PARA_ITEM_DLM)
        return((char *)NULL);
    strncpy(buf, p, PARA_ITEM_SIZE_CHAR);
    buf[PARA_ITEM_SIZE_CHAR]=ZERO;
    sscanf(buf, "%X", &itemsize);
    if(itemsize != itemdlm-sizedlm-1)
        return((char *)NULL);
    *p=ZERO;
    *itemdlm=ZERO;

    return(item);
}/*}}}*/

int para_insert_item(char *str, char *val)
{/*{{{*/
    int orgsize, valsize;
    char reserved;

    if(!str || !val)
        return(RET_ERR_PARA);

    orgsize=strlen(str);
    valsize=strlen(val);
    reserved=str[0];
    memmove(str+PARA_ITEM_SIZE_CHAR+1+valsize+1, str, orgsize+1);
    CLR_BUF(str);
    para_put_item(str, val); 
    str[PARA_ITEM_SIZE_CHAR+1+valsize+1]=reserved; 

    return(PARA_ITEM_SIZE_CHAR+1+valsize+1);
}/*}}}*/

int para_put_item(char *str, char *val)
{/*{{{*/
    int strsize, valsize;
    char *p;

    if(!str || !val)
        return(RET_ERR_PARA);

    strsize=strlen(str);
    if((valsize=strlen(val)) > PARA_ITEM_MAX_SIZE)
        return(RET_ERR_PARA);

    p=str+strsize;
    p+=sprintf(p, "%0*X", PARA_ITEM_SIZE_CHAR, valsize);
    *p=PARA_ITEM_DLM; p++;
    strcpy(p, val); p+=valsize;
    *p=PARA_ITEM_DLM; p++;
    *p=ZERO;

    return(PARA_ITEM_SIZE_CHAR+1+valsize+1);
}/*}}}*/

int para_put_item_int(char *str, int val)
{/*{{{*/
    char buf[50];
    sprintf(buf, "%d", val);
    return(para_put_item(str, buf));
}/*}}}*/

int para_put_item_int_fixed(char *str, int val, int width)
{/*{{{*/
    char buf[50];
    sprintf(buf, "%0*d", width, val);
    return(para_put_item(str, buf));
}/*}}}*/

int para_put_item_int64(char *str, int64_t val)
{/*{{{*/
    char buf[50];
    sprintf(buf, "%lld", (long long int)val);
    return(para_put_item(str, buf));
}/*}}}*/

int para_put_item_int64_fixed(char *str, int64_t val, int width)
{/*{{{*/
    char buf[50];
    sprintf(buf, "%0*lld", width, (long long int)val);
    return(para_put_item(str, buf));
}/*}}}*/

#define _ALLOC_NUM      10

struct _para_tmpl
{/*{{{*/
    struct _tmpl_entry
    {
        char *name;
        unsigned int type;
        unsigned int level;
        union
        {
            struct { int minlen; int maxlen; int num; int skip; } _array;
            struct { int minlen; int maxlen; } _number;
            struct { int minlen; int maxlen; } _string;
        } _u;
    } *entries;
    int entry_alloc_num;
    int entry_num;
    int root_num;
};/*}}}*/

struct _entry
{/*{{{*/
    char *name;
    unsigned int type;
    int len;
    char *value;
    struct _entry_array
    {
        struct _entry **array;
        int num;
        int tmpl_num;
    } array;
};/*}}}*/

struct _para
{/*{{{*/
    struct _para_tmpl tmpl;
    char *msg;  // 原消息复制
    struct _entry **seq;    // 顺序指针
    int seq_alloc_num;
    int seq_num;
    struct _entry_array root;   // 嵌套条目
};/*}}}*/

enum { _ARRAY_, _NUMBER_, _STRING_ };

void _free_tmpl(struct _para_tmpl *tmpl)
{/*{{{*/
    if(tmpl->entries)
    {
        int i;
        for(i=0; i<tmpl->entry_num; i++)
            free(tmpl->entries[i].name);
        free(tmpl->entries);
    }
}/*}}}*/

static int _load_tmpl(char *desc, struct _para_tmpl *tmpl)
{/*{{{*/
    struct _tmpl_entry *entry;
    struct _tmpl_entry *array_entry;
    char name[100], attr[100], type[20], size[80];
    unsigned int level;
    char *pbegin, *pend, *pcur, *p;
    int len;
    char minbuf[30], maxbuf[30];
    int rtn=0;
    int i, j;

    if(!desc)
        return(RET_ERR_PARA);
    if(!(pbegin=strdup(desc)))
        return(RET_ERR_ALLOC);
    str_replace(desc, SPACES, ZEROS, pbegin);
    ALLOC_ARRAY(tmpl->entries, struct _tmpl_entry,
            _ALLOC_NUM, tmpl->entry_alloc_num, OUT_GO);

    tmpl->entry_num=0;
    level=0;
    pend=pbegin+strlen(pbegin);
    pcur=pbegin;
    while(pcur != pend)
    {
        if(*pcur == '{')
        {   pcur++; level++; continue;   }
        if(*pcur == '}')
        {   pcur++; level--; continue;   }
        if(!(p=strchr(pcur, '(')))
        {   rtn=-1; goto OUT_GO;   }
        len=p-pcur;
        strncpy(name, pcur, p-pcur); name[len]=ZERO;
        pcur+=len+1;
        if(!(p=strchr(pcur, ')')))
        {   rtn=pcur-pbegin; break;   }
        len=p-pcur;
        strncpy(attr, pcur, p-pcur); attr[len]=ZERO;
        pcur+=len+1;
        entry=&tmpl->entries[tmpl->entry_num];
        if(strcasecmp(attr, "array") == 0)
        {
            entry->_u._array.minlen=1;
            entry->_u._array.maxlen=SHORT_NUMBER_SIZE;
            entry->type=_ARRAY_;
        }
        else
        {
            if((p=strchr(attr, ':')) == NULL)
            {   rtn=-1; goto OUT_GO;   }
            len=p-attr;
            strncpy(type, attr, len); type[len]=ZERO;
            len=strlen(attr)-len-1;
            strncpy(size, p+1, len); size[len]=ZERO;
            if(strcasecmp(type, "number") == 0)
            {
                if((p=strchr(size, '-')))
                {
                    len=p-size;
                    strncpy(minbuf, size, len); minbuf[len]=ZERO;
                    if((entry->_u._number.minlen=atoi(minbuf)) == 0)
                    {   rtn=-2; goto OUT_GO;   }
                    len=strlen(size)-len-1;
                    strncpy(maxbuf, p+1, len); maxbuf[len]=ZERO;
                    entry->_u._number.maxlen=atoi(maxbuf);
                    if(entry->_u._number.minlen >
                            entry->_u._number.maxlen ||
                            entry->_u._number.maxlen >
                            PARA_ITEM_MAX_SIZE)
                    {   rtn=-2; goto OUT_GO;   }
                }
                else
                {
                    if((len=atoi(size)) == 0)
                    {   rtn=-2; goto OUT_GO;   }
                    entry->_u._number.minlen=
                        entry->_u._number.maxlen=len;
                }
                entry->type=_NUMBER_;
            }
            else if(strcasecmp(type, "string") == 0)
            {
                if((p=strchr(size, '-')))
                {
                    len=p-size;
                    strncpy(minbuf, size, len); minbuf[len]=ZERO;
                    entry->_u._string.minlen=atoi(minbuf);
                    len=strlen(size)-len-1;
                    strncpy(maxbuf, p+1, len); maxbuf[len]=ZERO;
                    entry->_u._string.maxlen=atoi(maxbuf);
                    if(entry->_u._string.minlen >
                            entry->_u._string.maxlen ||
                            entry->_u._string.maxlen >
                            PARA_ITEM_MAX_SIZE)
                    {   rtn=-3; goto OUT_GO;   }
                }
                else
                {
                    if((len=atoi(size)) == 0)
                    {   rtn=-3; goto OUT_GO;   }
                    entry->_u._number.minlen=
                        entry->_u._number.maxlen=len;
                }
                entry->type=_STRING_;
            }
            else
            {   rtn=-5; goto OUT_GO;   }
        }
        entry->name=strdup(name);
        entry->level=level;
        tmpl->entry_num++;

        if(tmpl->entry_num == tmpl->entry_alloc_num)
        {
            EXPAND_ARRAY_DOERR(tmpl->entries,
                    struct _tmpl_entry, _ALLOC_NUM,
                    tmpl->entry_alloc_num,
                    rtn=RET_ERR_ALLOC; break);
            for(i=0; i<_ALLOC_NUM; i++)
                tmpl->entries[tmpl->entry_alloc_num-1-i].name=
                    (char *)NULL;
        }
    }
    if(level != 0)
    {   rtn=-6; goto OUT_GO;   }

    // 统计array的条目数
    tmpl->root_num=0;
    for(i=0; i<tmpl->entry_num; i++)
    {
        array_entry=&tmpl->entries[i];
        if(array_entry->level == 0)
            tmpl->root_num++;
        if(array_entry->type != _ARRAY_)
            continue;
        array_entry->_u._array.num=0;
        array_entry->_u._array.skip=0;
        for(j=i+1; j<tmpl->entry_num; j++)
        {
            entry=&tmpl->entries[j];
            if(entry->level == array_entry->level)
                break;
            if(entry->level == array_entry->level+1)
                array_entry->_u._array.num++;
            if(entry->level > array_entry->level)
                array_entry->_u._array.skip++;
        }
    }

OUT_GO:
    if(rtn)
        _free_tmpl(tmpl);
    free(pbegin);
    return(rtn);
}/*}}}*/

static int _load_array(char *msg, char **curr,
        struct _tmpl_entry *tmpl_array,
        struct _entry_array *entry_array, struct _para *para)
{/*{{{*/
    struct _entry **array;
    struct _entry *entry;
    int array_index;
    int array_level;
    int array_num;
    int tmpl_index;
    struct _tmpl_entry *tmpl_entry;
    int tmpl_array_num;
    char *value;
    int len;
    int res;
    int i;

    array_num=entry_array->num;
    if(array_num <= 0)
    {
        entry_array->array=(struct _entry **)NULL;        
        return(0);
    }

    ALLOC_MULTI_RETERR(entry_array->array, struct _entry *,
            array_num, RET_ERR_ALLOC);
    array=entry_array->array;
    for(array_index=0; array_index<array_num; array_index++)
        array[array_index]=(struct _entry *)NULL;

    tmpl_array_num=entry_array->tmpl_num;
    array_level=tmpl_array->level;

    // 数组条目数
    for(array_index=0; array_index<array_num; array_index++)
    {
        ALLOC_MULTI_RETERR(array[array_index], struct _entry,
                tmpl_array_num, RET_ERR_ALLOC);
        for(i=0; i<tmpl_array_num; i++)
            array[array_index][i].array.array=
                (struct _entry **)NULL;

        // 模板条目数
        tmpl_index=0;
        for(i=0; i<tmpl_array_num; i++)
        {
            tmpl_entry=&tmpl_array[tmpl_index];
            if(!(value=para_get_item(msg, curr)))
                return(para->seq_num+1);
            entry=&array[array_index][i];
            para->seq[para->seq_num++]=entry;
            if(para->seq_num == para->seq_alloc_num)
                EXPAND_ARRAY_RETERR(para->seq, struct _entry *,
                        _ALLOC_NUM, para->seq_alloc_num,
                        RET_ERR_ALLOC);
            entry->name=tmpl_entry->name;
            entry->type=tmpl_entry->type;
            len=strlen(value);
            entry->len=len;
            entry->value=value;
            switch(tmpl_entry->type)
            {
                case _ARRAY_:
                    if(len < tmpl_entry->_u._string.minlen ||
                            len > tmpl_entry->_u._string.maxlen)
                        return(para->seq_num);
                    entry->array.num=atoi(value);
                    entry->array.tmpl_num=
                        tmpl_entry->_u._array.num;
                    if((res=_load_array(msg, curr,
                                    &tmpl_array[tmpl_index+1],
                                    &entry->array, para)))
                        return(res);
                    tmpl_index+=tmpl_entry->_u._array.skip+1;
                    break;

                case _NUMBER_:
                    if(len < tmpl_entry->_u._string.minlen ||
                            len > tmpl_entry->_u._string.maxlen)
                        return(para->seq_num);
                    // 内容检查
                    tmpl_index+=1;
                    break;

                case _STRING_:
                    if(len < tmpl_entry->_u._string.minlen ||
                            len > tmpl_entry->_u._string.maxlen)
                        return(para->seq_num);
                    tmpl_index+=1;
                    break;
            }
        }
    }
    return(0);
}/*}}}*/

static void _free_array(struct _entry_array *entry_array)
{/*{{{*/
    if(entry_array->array)
    {
        int i, j;
        struct _entry *items;

        for(i=0; i<entry_array->num; i++)
        {
            if((items=entry_array->array[i]))
            {
                for(j=0; j<entry_array->tmpl_num; j++)
                    if(items[j].type == _ARRAY_)
                        _free_array(&items[j].array);
                free(items);
            }
        }
        free(entry_array->array);
    }
}/*}}}*/

int para_load(char *msg, char *desc, PARA *ppara)
{/*{{{*/
    struct _para *para;
    int rtn;
    int i;
    char *curr;
    int res;

    if(!ppara)
        return(RET_ERR_PARA);
    *ppara=(PARA)NULL;
    if(!msg || !desc)
        return(RET_ERR_PARA);

    ALLOC_RETERR(para, struct _para, RET_ERR_ALLOC);
    if((res=_load_tmpl(desc, &para->tmpl)))
    {
        free(para);
        return(res);
    }
    para->msg=(char *)NULL;
    para->seq=(struct _entry **)NULL;
    if(!(para->msg=strdup(msg)))
    {   free(para); return(RET_ERR_ALLOC);   }
    ALLOC_ARRAY_DOERR(para->seq, struct _entry *, para->tmpl.entry_num*2,
            para->seq_alloc_num,
            free(para->msg); free(para); return(RET_ERR_ALLOC));
    for(i=0; i<para->seq_alloc_num; i++)
        para->seq[i]=(struct _entry *)NULL;
    para->seq_num=0;

    curr=para->msg;
    para->root.num=1;
    para->root.tmpl_num=para->tmpl.root_num;
    rtn=_load_array(para->msg, &curr,
            &para->tmpl.entries[0], &para->root, para);
    if(rtn)
        para_free(para);
    else
        *ppara=para;
    return(rtn);
}/*}}}*/

void para_free(PARA para)
{/*{{{*/
    if(para)
    {
        _free_array(&para->root);
        free(para->seq);
        free(para->msg);
        _free_tmpl(&para->tmpl);
        free(para);
    }
}/*}}}*/

int para_num(PARA para)
{/*{{{*/
    if(!para)
        return(RET_ERR_PARA);
    return(para->seq_num);
}/*}}}*/

int para_ref_by_index(PARA para, int idx, char **pname, char **pvalue)
{/*{{{*/
    if(pname)
        *pname=(char *)NULL;
    if(!pvalue)
        return(RET_ERR_PARA);
    *pvalue=(char *)NULL;
    if(!para)
        return(RET_ERR_PARA);
    if(idx < 0)
       idx+=para->seq_num;
    if(idx < 0 || idx > para->seq_num-1)
        return(-1);
    if(pname)
        *pname=para->seq[idx]->name;
    *pvalue=para->seq[idx]->value;
    return(0);
}/*}}}*/

int para_copy_by_index(PARA para, int idx, char *name, char *value)
{/*{{{*/
    char *np, *vp;
    int res;

    if(!value)
        return(RET_ERR_PARA);
    CLR_BUF(value);
    if(name)
        CLR_BUF(name);
    if((res=para_ref_by_index(para, idx, &np, &vp)) == 0)
    {
        if(name)
            strcpy(name, np);
        strcpy(value, vp);
    }
    return(res);
}/*}}}*/

int para_ref_by_name(PARA para, char *name, char **pvalue)
{/*{{{*/
    int rtn=0;
    char *pbegin, *pcurr;
    char node[80];
    char s_entry[50];
    char s_ind[20];
    struct _entry *entry;
    int entry_ind;
    int entry_num;
    int ind;
    int num;
    int len;
    char *value;

    if(!pvalue)
        return(RET_ERR_PARA);
    *pvalue=(char *)NULL;
    if(!para || !name)
        return(RET_ERR_PARA);

    if(!(pbegin=strdup(name)))
        return(RET_ERR_ALLOC);
    str_replace(name, SPACES, ZEROS, pbegin);
    pcurr=pbegin;
    entry_num=para->root.tmpl_num;
    entry=para->root.array[0];
    value=(char *)NULL;
    while(sscanf(pcurr, "%[^.]", node) > 0)
    {
        // s_entry: NAME[].
        if(sscanf(node, "%[^[]", s_entry) <= 0)
        {   rtn=-1; goto OUT_GO;   }
        len=strlen(s_entry);
        // entry_ind: name index
        for(entry_ind=0; entry_ind<entry_num; entry_ind++)
            if(strcasecmp(entry[entry_ind].name, s_entry) == 0)
                break;
        if(entry_ind == entry_num)
        {   rtn=-2; goto OUT_GO;   }
        if(node[len] == '[')  // array
        {
            if(sscanf(node+len+1, "%[^]]", s_ind) <= 0) // [S_IND]
            {   rtn=-3; goto OUT_GO;   }
            if(!str_is_dec((s_ind[0] == '+' || s_ind[0] == '-') ?
                        s_ind+1 : s_ind))
            {   rtn=-4; goto OUT_GO;   }
            ind=atoi(s_ind);    // array index in data
            num=entry[entry_ind].array.num; // array num in data
            if(ind < 0)
                ind+=num;
            if(ind < 0 || ind > num-1)
            {   rtn=-5; goto OUT_GO;   }
            value=entry[entry_ind].value;
            entry_num=entry[entry_ind].array.tmpl_num;
            entry=entry[entry_ind].array.array[ind];
        }
        else    // number string
        {
            value=entry[entry_ind].value;
            break;
        }
        pcurr+=strlen(node);
        if(*pcurr == ZERO)
            break;
        pcurr++;
    }
    *pvalue=value;

OUT_GO:
    free(pbegin);
    return(rtn);
}/*}}}*/

int para_copy_by_name(PARA para, char *name, char *value)
{/*{{{*/
    char *vp;
    int res;

    if(!value)
        return(RET_ERR_PARA);
    CLR_BUF(value);
    if((res=para_ref_by_name(para, name, &vp)) == 0)
        strcpy(value, vp);
    return(res);
}/*}}}*/
